package ca.ubc.eece310.project;

/**
 * EECE 310 Term Project Fall 2009
 * @author Matthew Fong, Herman Lee, Scott Pearson
 */

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.ConcurrentModificationException;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jnetpcap.Pcap;
import org.jnetpcap.PcapIf;

public class BandwidthLog {

	/*******************************************************************************
	 * OVERVIEW: Monitors the bandwidth usage of a user selected network
	 * interface and keeps track of how much data is uploaded and downloaded per
	 * day
	 *******************************************************************************/

	private static Iterator<MyPort> portIterator = MyPort.MyPorts.iterator();
	private static Iterator<RunningProcess> processIterator = RunningProcess.RunningProcesses.iterator();
	private static List<PcapIf> alldevs = new ArrayList<PcapIf>();
	private static StringBuilder errbuf = new StringBuilder();
	private static DecimalFormat format = new DecimalFormat("##.##");
	private static int garbageCollect = 0;

	public static HashSet<MyPacket> lastPackets;
	public static PcapIf device;
	public static int timePassed;
	public static String ip, subnetmask;
	public static double totalUp, totalDown;
	public static Long totalBytesUpToday = 0L, totalBytesDownToday = 0L;
	public static Long totalKBytesUpMonth = 0L, totalKBytesDownMonth = 0L;
	public static Double totalMBytesUpYear = 0.0, totalMBytesDownYear = 0.0;

	public static ReentrantLock lock = new ReentrantLock();

	@SuppressWarnings("deprecation")
	public static void main(String[] args) {

		Thread t = new Thread(new ConnectionGetter());
		t.start();

	// start: get a list of the devices
		int r = Pcap.findAllDevs(alldevs, errbuf);
		if (r == Pcap.NOT_OK || alldevs.isEmpty()) {
			System.err.printf("Can't read list of devices, error is %s\n",
					errbuf.toString());
		}
		device = alldevs.get(0);
	// end: get a list of the devices
	// get the ip address and subnet mask for the selected interface (0 at this point)
		getAddresses();

		Gui.createAndShowGUI();

		Thread logger = new Thread(new Log());
		logger.start();
		
		lastPackets = new HashSet<MyPacket>();

		while (true) {
			clearPast();
			device = alldevs.get(Gui.device.getSelectedIndex());
			getAddresses();

		// start: sniff for packets
			Thread packetSniffer = new Thread(new PacketSniffer(device));
			packetSniffer.start();
			// packets are put in HashSet<MyPacket> lastPackets
			try {
				packetSniffer.join(1500);
			} catch (InterruptedException e) {}
			packetSniffer.stop();
		// end: sniff for packets
			
		// start: organize packets to each open port
			Iterator<MyPacket> packetIterator = lastPackets.iterator();
			int packetSourcePort, packetDestinationPort, portPort;
			try {
				while (packetIterator.hasNext()) {
					MyPacket packet = packetIterator.next();
					portIterator = MyPort.MyPorts.iterator();

					packetSourcePort = packet.getSourcePort();
					packetDestinationPort = packet.getDestinationPort();

					while (portIterator.hasNext()) {
						MyPort port = portIterator.next();
						portPort = port.getPort();

						if (packetSourcePort == portPort || packetDestinationPort == portPort) {
							port.getPackets().add(packet);
						}
					}
				}
			} catch (ConcurrentModificationException e) {}
		// end: organize packets to each open port

		// start: calculate total speed for each port
			portIterator = MyPort.MyPorts.iterator();
			while (portIterator.hasNext()) {
				MyPort port = portIterator.next(); 
				packetIterator = port.getPackets().iterator();
				while (packetIterator.hasNext()) {
					MyPacket packet = packetIterator.next(); 
					if (packet.getSource().equals(ip))
						port.setUp(port.getUp() + packet.getSize());
					else if (packet.getDestination().equals(ip))
						port.setDown(port.getDown() + packet.getSize());
				}
				int unit = 1;
				switch (Gui.units.getSelectedIndex()) {
					case 0:
						unit = 1;
						break;
					case 1:
						unit = 1024;
						break;
					case 2:
						unit = 1048576;
						break;
				}
				double portUp = port.getUp(), portDown = port.getDown();
				if (portUp != Double.NaN
						&& portUp != Double.POSITIVE_INFINITY
						&& portUp != Double.NEGATIVE_INFINITY)
					totalBytesUpToday += Math.round(port.getUp());
				
				if (portDown != Double.NaN
						&& portDown != Double.POSITIVE_INFINITY
						&& portDown != Double.NEGATIVE_INFINITY)
					totalBytesDownToday += Math.round(port.getDown()); 
				
				if (port.getUp() != 0 && port.getUp() != Double.NaN
									  && port.getUp() != Double.POSITIVE_INFINITY)
					port.setUp((port.getUp() + 1) / ((timePassed + 1) / 1000)/ unit);
				else
					port.setUp(0.0);
				
				if (port.getDown() != 0 && port.getDown() != Double.NaN
										&& port.getDown() != Double.POSITIVE_INFINITY)
					port.setDown((port.getDown() + 1) / ((timePassed + 1) / 1000) / unit);
				else
					port.setDown(0.0);
				
			}
		// end: calculate total speed for each port
  
			lock.lock(); 
		// start: calculate total speed for each process from port speeds
			processIterator = RunningProcess.RunningProcesses.iterator();
			while (processIterator.hasNext()) {
				RunningProcess process = processIterator.next();
				Iterator<MyPort> processPortIterator = process.getPorts().iterator();
				
				while (processPortIterator.hasNext()) {
					MyPort processPort = processPortIterator.next();
					portIterator = MyPort.MyPorts.iterator();
					while (portIterator.hasNext()) {
						MyPort port = portIterator.next();
						if (processPort.getPort() == port.getPort()) {
							process.setUp(process.getUp()+port.getUp());
							process.setDown(process.getDown()+port.getDown());
						}
					}
				}
			}
		// end: calculate total speed for each process from port speeds
			lock.unlock(); 
			
		// start: calculate total speed
			processIterator = RunningProcess.RunningProcesses.iterator(); 
			while (processIterator.hasNext()) {
				RunningProcess process = processIterator.next(); 
				totalUp += process.getUp();
				totalDown += process.getDown();
			}
		// end: calculate total speed

			Gui.update();
		}
	}

	public static void clearPast() {
		/*******************************************************************************
		 * REQUIRES: lastPackets is initialized, portIterator must be declared,
		 *           processIterator must be declared, device must be declared, totalUp
		 *           must be declared, totalDown must be declared 
		 * MODIFIES: lastPackets, portIterator, processIterator, device, totalUp, 
		 *           totalDown 
		 * EFFECTS:  Clears the history of all variables referred to in the MODIFIES
		 * 			 clause.
		 *******************************************************************************/
		lastPackets.clear();
		if(garbageCollect%1500==0)
			System.gc();
		garbageCollect = garbageCollect%1500+1;
		portIterator = MyPort.MyPorts.iterator();
		while (portIterator.hasNext()) {
			portIterator.next().clearPackets();
		}
		processIterator = RunningProcess.RunningProcesses.iterator();
		while (processIterator.hasNext()) {
			processIterator.next().clearSpeed();
		}
		device = null;
		totalUp = 0;
		totalDown = 0;
	}

	public static void getAddresses() {
		/*******************************************************************************
		 * REQUIRES: device != null 
		 * MODIFIES: ip, subnetMask 
		 * EFFECTS:  Gets the IP address and subnet mask of the currently selected network
		 * 			 interface
		 *******************************************************************************/
		// get address and subnet mask
		String addresses = device.getAddresses().toString(); 
		Pattern pat = Pattern .compile("addr=\\[INET4:\\d*\\.\\d*\\.\\d*\\.\\d*\\]");
		Matcher m = pat.matcher(addresses);
		while (m.find()) {
			ip = m.group().substring(m.group().indexOf(":") + 1, m.group().length() - 1);
		}
		pat = Pattern.compile("mask=\\[INET4:\\d*\\.\\d*\\.\\d*\\.\\d*\\]");
		m = pat.matcher(addresses);
		while (m.find()) {
			subnetmask = m.group().substring(m.group().indexOf(":") + 1,
					m.group().length() - 1);
		}
	}

	public static List<PcapIf> getDevices() {
		/*******************************************************************************
		 * EFFECTS: Returns the devices on this computer
		 *******************************************************************************/
		return alldevs;
	}
	
	public static String[] currentProjectedToday() {
		Calendar c = new GregorianCalendar();
		String[] result = new String[6];
		Double[] resultD = new Double[6];
		// [currentdown][currentup][currenttotal]
		// [projectdown][projectup][projecttotal]
		int numMinutes = c.get(Calendar.HOUR_OF_DAY)*60 +c.get(Calendar.MINUTE);
		resultD[0] = Double.parseDouble(totalBytesDownToday.toString());
		resultD[1] = Double.parseDouble(totalBytesUpToday.toString());
		resultD[2] = resultD[0] + resultD[1];
		if (resultD[0] > (1024 * 512L) && resultD[0] < (1024 * 1024 * 512L)) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[0] = format.format(resultD[0] / 1024 / 1024.0) + " MiB"; 
		} else if (resultD[0] > (1024 * 1024 * 512L)) {
			// totalDownThisMonth > 500MiB
			result[0] = format.format(resultD[0] / 1024 / 1024 / 1024.0) + " GiB"; 
		} else {
			result[0] = format.format(resultD[0] / 1024.0) + " KiB"; 
		}
		
		if (resultD[1] > (1024 * 512L) && resultD[1] < (1024 * 1024 * 512L)) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[1] = format.format(resultD[1] / 1024 / 1024.0) + " MiB"; 
		} else if (resultD[1] > (1024 * 1024 * 512L)) {
			// totalDownThisMonth > 500MiB
			result[1] = format.format(resultD[1] / 1024 / 1024 / 1024.0) + " GiB"; 
		} else {
			result[1] = format.format(resultD[1] / 1024.0) + " KiB"; 
		}
		
		if (resultD[2] > (1024 * 512L) && resultD[2] < (1024 * 1024 * 512L)) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[2] = format.format(resultD[2] / 1024 / 1024.0) + " MiB"; 
		} else if (resultD[2] > (1024 * 1024 * 512L)) {
			// totalDownThisMonth > 500MiB
			result[2] = format.format(resultD[2] / 1024 / 1024 / 1024.0) + " GiB"; 
		} else {
			result[2] = format.format(resultD[2] / 1024.0) + " KiB"; 
		}
		
		resultD[3] = Double.parseDouble(totalBytesDownToday.toString());
		resultD[4] = Double.parseDouble(totalBytesUpToday.toString());
		resultD[3] = resultD[3]/(numMinutes+1)*1440.0;
		resultD[4] = resultD[4]/(numMinutes+1)*1440.0;
		resultD[5] = resultD[3] + resultD[4];
		if (resultD[3] > (1024 * 512L)
				&& resultD[3] < (1024 * 1024 * 512L)) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[3] = format.format(resultD[3] / 1024 / 1024.0) + " MiB"; 
		} else if (resultD[3] > (1024 * 1024 * 512L)) {
			// totalDownThisMonth > 500MiB
			result[3] = format.format(resultD[3] / 1024 / 1024 / 1024.0) + " GiB"; 
		} else {
			result[3] = format.format(resultD[3] / 1024.0) + " KiB"; 
		}
		
		if (resultD[4] > (1024 * 512L) && resultD[4] < (1024 * 1024 * 512L)) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[4] = format.format(resultD[4] / 1024 / 1024.0) + " MiB"; 
		} else if (resultD[4] > (1024 * 1024 * 512L)) {
			// totalDownThisMonth > 500MiB
			result[4] = format.format(resultD[4] / 1024 / 1024 / 1024.0) + " GiB"; 
		} else {
			result[4] = format.format(resultD[4] / 1024.0) + " KiB"; 
		}
		
		if (resultD[5] > (1024 * 512L) && resultD[5] < (1024 * 1024 * 512L)) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[5] = format.format(resultD[5] / 1024 / 1024.0) + " MiB"; 
		} else if (resultD[5] > (1024 * 1024 * 512L)) {
			// totalDownThisMonth > 500MiB
			result[5] = format.format(resultD[5] / 1024 / 1024 / 1024.0) + " GiB"; 
		} else {
			result[5] = format.format(resultD[5] / 1024.0) + " KiB"; 
		}
		return result;
	}
	
	public static String[] currentProjectedMonth() {
		Calendar c = new GregorianCalendar();
		String[] result = new String[6];
		Double[] resultD = new Double[6];
		// [currentdown][currentup][currenttotal]
		// [projectdown][projectup][projecttotal]
		int numDays = Log.getNumLoggedDaysThisMonth();
		resultD[0] = Double.parseDouble(totalKBytesDownMonth.toString());
		resultD[1] = Double.parseDouble(totalKBytesUpMonth.toString()); 
		resultD[2] = resultD[0] + resultD[1];
		if (resultD[0] > (512L) && resultD[0] < (1024 * 512L)) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[0] = format.format(resultD[0] / 1024.0) + " MiB"; 
		} else if (resultD[0] > (1024 * 512L)) {
			// totalDownThisMonth > 500MiB
			result[0] = format.format(resultD[0] / 1024 / 1024.0) + " GiB"; 
		} else {
			result[0] = format.format(resultD[0]) + " KiB"; 
		}
		
		if (resultD[1] > (512L) && resultD[1] < (1024 * 512L)) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[1] = format.format(resultD[1] / 1024.0) + " MiB"; 
		} else if (resultD[1] > (1024 * 512L)) {
			// totalDownThisMonth > 500MiB
			result[1] = format.format(resultD[1] / 1024 / 1024.0) + " GiB"; 
		} else {
			result[1] = format.format(resultD[1]) + " KiB"; 
		}
		
		if (resultD[2] > (512L) && resultD[2] < (1024 * 512L)) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[2] = format.format(resultD[2] / 1024.0) + " MiB"; 
		} else if (resultD[2] > (1024 * 512L)) {
			// totalDownThisMonth > 500MiB
			result[2] = format.format(resultD[2] / 1024 / 1024.0) + " GiB"; 
		} else {
			result[2] = format.format(resultD[2]) + " KiB"; 
		}
		resultD[3] = Double.parseDouble(totalKBytesDownMonth.toString());
		resultD[4] = Double.parseDouble(totalKBytesUpMonth.toString()); 
		resultD[3] = resultD[3]/(numDays+1)*c.getActualMaximum(Calendar.DAY_OF_MONTH);
		resultD[4] = resultD[4]/(numDays+1)*c.getActualMaximum(Calendar.DAY_OF_MONTH);
		resultD[5] = resultD[3] + resultD[4];
		if (resultD[3] > (512L) && resultD[3] < (1024 * 512L)) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[3] = format.format(resultD[0] / 1024.0) + " MiB"; 
		} else if (resultD[3] > (1024 * 512L)) {
			// totalDownThisMonth > 500MiB
			result[3] = format.format(resultD[3] / 1024 / 1024.0) + " GiB"; 
		} else {
			result[3] = format.format(resultD[3]) + " KiB"; 
		}
		
		if (resultD[4] > (512L) && resultD[4] < (1024 * 512L)) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[4] = format.format(resultD[4] / 1024.0) + " MiB"; 
		} else if (resultD[4] > (1024 * 512L)) {
			// totalDownThisMonth > 500MiB
			result[4] = format.format(resultD[4] / 1024 / 1024.0) + " GiB"; 
		} else {
			result[4] = format.format(resultD[4]) + " KiB"; 
		}
		
		if (resultD[5] > (512L) && resultD[5] < (1024 * 512L)) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[5] = format.format(resultD[5] / 1024.0) + " MiB"; 
		} else if (resultD[5] > (1024 * 512L)) {
			// totalDownThisMonth > 500MiB
			result[5] = format.format(resultD[5] / 1024 / 1024.0) + " GiB"; 
		} else {
			result[5] = format.format(resultD[2]) + " KiB"; 
		}
		return result;
	}
	
	public static String[] currentProjectedYear() {
		String[] result = new String[6];
		Double[] resultD = new Double[6];
		// [currentdown][currentup][currenttotal]
		// [projectdown][projectup][projecttotal]
		resultD[0] = Double.parseDouble(totalMBytesDownYear.toString());
		resultD[1] = Double.parseDouble(totalMBytesUpYear.toString());
		resultD[2] = resultD[0] + resultD[1];
		if (resultD[0] < 500) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[0] = format.format(resultD[0]) + " MiB"; 
		} else {
			// totalDownThisMonth > 500MiB
			result[0] = format.format(resultD[0] / 1024.0) + " GiB"; 
		}
		
		if (resultD[1] < 500) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[1] = format.format(resultD[1]) + " MiB"; 
		} else {
			// totalDownThisMonth > 500MiB
			result[1] = format.format(resultD[1] / 1024.0) + " GiB"; 
		}
		
		if (resultD[2] < 500) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[2] = format.format(resultD[2]) + " MiB"; 
		} else {
			// totalDownThisMonth > 500MiB
			result[2] = format.format(resultD[2] / 1024.0) + " GiB"; 
		}
		
		int numDays = Log.getNumLoggedDaysThisYear();
		resultD[3] = Double.parseDouble(totalMBytesDownYear.toString());
		resultD[4] = Double.parseDouble(totalMBytesUpYear.toString());
		resultD[3] = resultD[3]/(numDays+1)*365.0;
		resultD[4] = resultD[4]/(numDays+1)*365.0;
		resultD[5] = resultD[3] + resultD[4];
		if (resultD[3] < 500) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[3] = format.format(resultD[3]) + " MiB"; 
		} else  {
			// totalDownThisMonth > 500MiB
			result[3] = format.format(resultD[3] / 1024.0) + " GiB"; 
		}
		
		if (resultD[4] < 500) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[4] = format.format(resultD[4]) + " MiB"; 
		} else  {
			// totalDownThisMonth > 500MiB
			result[4] = format.format(resultD[4] / 1024.0) + " GiB"; 
		}
		
		if (resultD[5] < 500) {
			// 500KiB < totalDownThisMonth < 500MiB
			result[5] = format.format(resultD[2]) + " MiB"; 
		} else {
			// totalDownThisMonth > 500MiB
			result[5] = format.format(resultD[5] / 1024.0) + " GiB"; 
		}
		return result;
	}

}
